package Impl;

import java.io.BufferedReader;
import java.io.FileReader;

import Contracts.TerrainServiceContract;
import Exceptions.PreCondException;
import Services.BlocService;
import Services.TerrainService;

public class Terrain implements TerrainService {
	private int sizeX;
	private int sizeY;
	private int xJoueur;
	private int yJoueur;
	private int nbDiamant;
	private int nbPorte;
	private int nbJoueur;
	private boolean isStarted;
	private boolean isChanged[][];
	private BlocService[][] plateau;

	public Terrain(){}
	
	// Constructeurs
	@Override
	public void init(int sizeX, int sizeY) {
		if(sizeX<=1 || sizeY<=1)
			throw new PreCondException("pre cond 1");
		
		this.sizeX = sizeX;
		this.sizeY = sizeY;
		this.xJoueur = 1;
		this.yJoueur = 1;
		this.nbDiamant = 0;
		this.nbPorte = 0;
		this.nbJoueur = 1;
		this.plateau = new Bloc[sizeX][sizeY];
		this.isChanged = new boolean[sizeX][sizeY];
		this.isStarted = false;
		
		// Remplissage du tableau par des cases Vides
		for(int i = 0; i<sizeX; i++){
			for(int j = 0; j<sizeY; j++){
				plateau[i][j] = new Bloc();
				
				if((xJoueur-1)==i && (yJoueur-1)==j){
					plateau[i][j].init(Type.Hero);
				}
				else
					plateau[i][j].init(Type.Vide);
				
				isChanged[i][j] = false;
			}
		}
	}
	
	public void init(String fileName){
		try {
			FileReader fr = new FileReader(fileName);
			BufferedReader input = new BufferedReader(fr);
			
			String[] sizes = input.readLine().split(" ");
			this.sizeX = Integer.parseInt(sizes[0]);
			this.sizeY = Integer.parseInt(sizes[1]);
			this.xJoueur = 0;
			this.yJoueur = 0;
			this.nbDiamant = 0;
			this.nbPorte = 0;
			this.plateau = new Bloc[sizeX][sizeY];
			this.isChanged = new boolean[sizeX][sizeY];
			this.isStarted = false;
			
			String[] ligne;
			for(int y = 0; y<sizeY; y++){
				ligne = input.readLine().split(" ");
				
				for(int x = 0; x<sizeX; x++){
					plateau[x][y] = new Bloc();
					
					if(ligne[x].equals("Vide"))
						plateau[x][y].init(Type.Vide);
					else if(ligne[x].equals("Mur"))
						plateau[x][y].init(Type.Mur);
					else if(ligne[x].equals("Hero")){
						this.xJoueur = x+1;
						this.yJoueur = y+1;
						this.nbJoueur++;
						plateau[x][y].init(Type.Hero);
					}
					else if(ligne[x].equals("Diamant")){
						plateau[x][y].init(Type.Diamant);
						nbDiamant++;
					}
					else if(ligne[x].equals("Terre"))
						plateau[x][y].init(Type.Terre);
					else if(ligne[x].equals("SortieFermee")){
						plateau[x][y].init(Type.SortieFermee);
						nbPorte++;
					}
					else if(ligne[x].equals("Rocher"))
						plateau[x][y].init(Type.Rocher);
					//else if(ligne[x].equals("SortieOuverte"))
						//plateau[x][y].init(Type.SortieOuverte);
					isChanged[x][y] = false;
				}
			}
		} catch (Exception e) {
			e.printStackTrace();
		}	
	}
	
	public void clone(TerrainService terrain){
		init(terrain.getSizeX(), terrain.getSizeY());
		this.nbDiamant=terrain.getNbDiamant();
		this.nbJoueur=terrain.getNbJoueur();
		this.nbPorte=terrain.getNbPorte();
		this.xJoueur=terrain.getXJoueur();
		this.yJoueur=terrain.getYJoueur();
		this.isStarted=terrain.isStarted();
		
		for(int i = 1; i<=sizeX; i++){
			for(int j = 1; j<=sizeY; j++){
				isChanged[i-1][j-1] = terrain.isChanged(i, j);
				getBloc(i,j).clone(terrain.getBloc(i, j));
			}
		}
	}
	
	
	// Observateurs
	@Override
	public int getSizeX() {
		return sizeX;
	}

	@Override
	public int getSizeY() {
		return sizeY;
	}

	@Override
	public BlocService getBloc(int xold, int yold) {
		int x = convertX(xold);
		int y = convertY(yold);
		return plateau[x-1][y-1];
	}

	@Override
	public Type getTypeBloc(int xold, int yold) {
		int x = convertX(xold);
		int y = convertY(yold);
		return plateau[x-1][y-1].getType();
	}

	@Override
	public Type getOldTypeBloc(int xold, int yold) {
		int x = convertX(xold);
		int y = convertY(yold);
		return plateau[x-1][y-1].getOldType();
	}

	@Override
	public int getXJoueur() {
		return xJoueur;
	}

	@Override
	public int getYJoueur() {
		return yJoueur;
	}

	@Override
	public int getNbDiamant() {
		return nbDiamant;
	}

	@Override
	public int getNbPorte() {
		return nbPorte;
	}
	
	@Override
	public int getNbJoueur() {
		return nbJoueur;
	}
	
	@Override
	public boolean isChanged(int xold, int yold) {
		int x = convertX(xold);
		int y = convertY(yold);
		return isChanged[x-1][y-1];
	}

	@Override
	public boolean isStarted() {
		return this.isStarted;
	}
	
	// Operateurs
	@Override
	public void startTerrain() {
		if(isStarted()) throw new PreCondException("pre startTerrain");
		if(getNbPorte()!=1) throw new PreCondException("pre startTerrain");
		if(getNbDiamant()<=0) throw new PreCondException("pre startTerrain");
		if(getNbJoueur()!=1) throw new PreCondException("pre startTerrain");
		
		if(nbPorte!=1 || getNbJoueur()!=1 || nbDiamant<=0)
			isStarted=false;
		else 
			isStarted=true;
	}

	@Override
	public void validate(){
		if(	!isStarted() ) throw new PreCondException("pre validate");
		
		for(int i = 0; i<sizeX; i++)
			for(int j = 0; j<sizeY; j++)
				isChanged[i][j] = false;
	}

	@Override
	public void openDoor(){
		if(!isStarted() || getNbDiamant()!=0) throw new PreCondException("pre openDoor");
		
		for(int i = 1; i<=sizeX; i++)
			for(int j = 1; j<=sizeY; j++)
				if(getTypeBloc(i,j)==Type.SortieFermee)
					getBloc(i,j).ChangeType(Type.SortieOuverte);
	}
	
	@Override
	public void addObject(int x, int y, Type type) { 
		if(x<1 || x>getSizeX() || y<1 || y>getSizeY()) throw new PreCondException("pre addObject");
		if(type==Type.Hero || type==Type.SortieOuverte) throw new PreCondException("pre addObject");
		if(getTypeBloc(x,y)!=Type.Vide) throw new PreCondException("pre addObject");
		if(isStarted()) throw new PreCondException("pre addObject");
		
		if(type==Type.SortieFermee)
			nbPorte++;
		else if(type==Type.Diamant)
			nbDiamant++;
		getBloc(x,y).ChangeType(type);
	}

	@Override
	public void putHero(int xold, int yold) {
		if(! (0<=xold && xold<=getSizeX()+1))
			throw new PreCondException("pre moveObject");
		if(! (0<=yold && yold<=getSizeY()+1))
			throw new PreCondException("pre moveObject");
		
		int x = convertX(xold);
		int y = convertY(yold);
		if(getTypeBloc(x,y)==Type.Diamant)
			nbDiamant--;
		getBloc(getXJoueur(),getYJoueur()).ChangeType(Type.Vide);
		xJoueur = x;
		yJoueur = y;
		getBloc(getXJoueur(),getYJoueur()).ChangeType(Type.Hero);
	}

	@Override
	public void moveObject(int xold, int yold, int xnewold, int ynewold) {
		if(getTypeBloc(xold,yold)!=Type.Rocher && getTypeBloc(xold,yold)!=Type.Diamant) 
			throw new PreCondException("pre moveObject");
		if(getTypeBloc(xnewold,ynewold)!=Type.Hero && getTypeBloc(xnewold,ynewold)!=Type.Vide)
			throw new PreCondException("pre moveObject");
		if(!(!isChanged(xold,yold)))
			throw new PreCondException("pre moveObject");
		if(! isStarted())
			throw new PreCondException("pre moveObject");
			
		int x=convertX(xold);
		int y=convertY(yold);
		int xnew=convertX(xnewold);
		int ynew=convertY(ynewold);
		
		getBloc(xnew, ynew).ChangeType(getTypeBloc(x, y));
		isChanged[xnew-1][ynew-1] = true;
		getBloc(x, y).ChangeType(Type.Vide);
	}
	
	@Override
	public boolean equals(Object terrainold){
		if(!(terrainold instanceof TerrainService || terrainold instanceof TerrainServiceContract)){
			System.out.println("ok");
			return false;
		}
		TerrainService terrain = (TerrainService) terrainold;
		if(getNbDiamant()!=terrain.getNbDiamant())
			return false;
		if(getNbJoueur()!=terrain.getNbJoueur())
			return false;
		if(getNbPorte()!=terrain.getNbPorte())
			return false;
		if(getSizeX()!=terrain.getSizeX())
			return false;
		if(getSizeY()!=terrain.getSizeY())
			return false;
		if(getXJoueur()!=terrain.getXJoueur())
			return false;
		if(getYJoueur()!=terrain.getYJoueur())
			return false;
		
		for(int x=1; x<=getSizeX(); x++){
			for(int y=1; y<=getSizeY(); y++){
				if(! getBloc(x,y).equals(terrain.getBloc(x, y)))
					return false;
				if(! isChanged(x,y)==terrain.isChanged(x, y))
					return false;
			}
		}
		
		return true;
	}

	private int convertX(int x){
		if(x<1)
			return convertX(getSizeX()+x);
		else if(x>getSizeX())
			return x%getSizeX();
		else
			return x;
	}
	
	private int convertY(int y){
		if(y<1)
			return convertY(getSizeY()+y);
		else if(y>getSizeY())
			return y%getSizeY();
		else
			return y;
	}
	

}
